package super

import (
	"fmt"
	"net"
	"runtime/debug"
	"sync"
	"time"

	"gddgame.cc/galaxy/satellite"
	"gddgame.cc/galaxy/satellite/transport/tcp"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

type Socket struct {
	def.Logger

	layer  func(msg []byte)
	close  <-chan struct{}
	socket *satellite.Super

	listening bool
	nodes     map[string]chan def.Stream
	mux       sync.RWMutex
}

func NewSocket(logger def.Logger) *Socket {
	l := &Socket{
		Logger:    logger,
		listening: false,
		nodes:     make(map[string]chan def.Stream, 3),
		mux:       sync.RWMutex{},
	}
	return l
}
func (l *Socket) ID() string {
	return "socket"
}
func (l *Socket) Bind(layer func(msg []byte), close <-chan struct{}) {
	l.layer = layer
	l.close = close
}

func (l *Socket) Listen(node string) error {
	l.listening = true
	if l.socket == nil {
		socket, err := receiveSocket(node)
		if err != nil {
			return err
		}
		socket.SetLogger(l)
		l.socket = socket
	}
	go func() {
		var msgB []byte
		var err error
		for {
			msgB, err = l.socket.Recv()
			if !l.listening {
				return
			}
			if err != nil {
				continue
			}
			l.layer(msgB)
		}
	}()
	return nil
}
func (l *Socket) Connect(node string) error {
	l.mux.Lock()
	if _, ok := l.nodes[node]; ok {
		l.mux.Unlock()
		return nil
	}
	socket, err := sendSocket()
	if err != nil {
		l.mux.Unlock()
		return err
	}
	socket.SetLogger(l)
	if err = sendNode(socket, node); err != nil {
		l.mux.Unlock()
		return err
	}
	c := make(chan def.Stream, 30)
	l.nodes[node] = c
	l.mux.Unlock()
	//l.Debugf("connect Node socket:%s", node)
	open := make(chan struct{}, 1)
	go func() {
		open <- struct{}{}
		thread := utils.NewThread(l, 30)
		thread.SetThread(true)
		clean := make(chan struct{}, 1)
		for {
			tt := time.NewTimer(time.Hour)
			select {
			case s := <-c:
				thread.Thread(func() {
					b, err := s.Payload()
					if err != nil {
						//fmt.Println(err)
						s.Release()
						return
					}
					//fmt.Println("send 2")
					err = socket.Send(b)
					//fmt.Println("send 3")
					s.Release()
					if err == satellite.SuperClose {
						if clean != nil {
							clean <- struct{}{}
						}
					} else if err != nil {
						fmt.Println(err)
						debug.PrintStack()
					}
				})
			case <-clean:
				tt.Stop()
				goto Clean
			case <-l.close:
				tt.Stop()
				goto Clean
			case <-tt.C:
				tt.Stop()
				goto Clean
			}
			tt.Stop()
		}
	Clean:
		//fmt.Println("clean", node)
		thread.CleanThread()
		close(clean)
		clean = nil
		l.mux.Lock()
		_ = socket.Close()
		delete(l.nodes, node)
		l.mux.Unlock()
	}()
	<-open
	return nil
}
func (l *Socket) Close() error {
	if !l.listening {
		return nil
	}
	l.listening = false
	return l.socket.Close()
}

func (l *Socket) Get(node string) chan def.Stream {
	l.mux.RLock()
	c, ok := l.nodes[node]
	l.mux.RUnlock()
	if !ok {
		//debug.PrintStack()
		_ = l.Connect(node)
	}
	return c
}

func (l *Socket) IsConnect(node string) bool {
	l.mux.RLock()
	_, ok := l.nodes[node]
	l.mux.RUnlock()
	return ok
}

func (l *Socket) Nodes() []string {
	nodes := make([]string, 0, len(l.nodes))
	l.mux.RLock()
	for node := range l.nodes {
		nodes = append(nodes, node)
	}
	l.mux.RUnlock()
	return nodes
}

func receiveSocket(node string) (*satellite.Super, error) {
	addrTcp, err := net.ResolveTCPAddr("tcp", node)
	if err != nil {
		return nil, err
	}
	sock := satellite.NewSuper()
	if err := sock.Listen(); err != nil {
		return nil, err
	}
	if err := sock.AddTransport(tcp.NewTransport(addrTcp)); err != nil {
		return nil, err
	}
	return sock, nil
}

func sendSocket() (*satellite.Super, error) {
	sock := satellite.NewSuper()
	if err := sock.Dial(); err != nil {
		return nil, err
	}
	return sock, nil
}

func sendNode(sock *satellite.Super, node string) error {
	addrTcp, err := net.ResolveTCPAddr("tcp", node)
	if err != nil {
		return err
	}
	return sock.AddTransport(tcp.NewTransport(addrTcp))
}
